﻿/****************************************************************************************************************
*                                                                                                               *
* Copyright (C) 2011 5173.com                                                                                   *
* This project may be copied only under the terms of the Apache License 2.0.                                    *
* Please visit the project Home Page http://bqqapicsharp.codeplex.com/ for more detail.                         *
*                                                                                                               *
****************************************************************************************************************/

namespace BQQAPIClient.Core.Network
{
    using System;
    using System.Net;
    using System.Net.Sockets;
    using System.Text;
    using System.Threading;
    using BQQAPIClient.Core.Protocol;
    using BQQAPIClient.Core.Utility;

    internal class Server
    {       
        private Semaphore receiveEnforcer;
        private Semaphore sendEnforcer;
        private Socket listenSocket;
        private SocketAsyncEventArgsPool poolOfReceiveEventArgs;
        private BufferManager receiveBufferManager;
        private IPEndPoint localEndPoint;
        private IPEndPoint remoteEndPoint;
        internal ServerPerformanceCounter ServerPerformanceCounter { private set; get; }
        internal ServerConfiguration ServerConfiguration { private set; get; }
        private bool init = false;
        private bool enable = true;

        public Server()
            : this(ServerConfigurationProvider.GetServerConfigurationFromConfigFile()) { }

        public Server(ServerConfiguration serverConfiguration)
        {
            try
            {
                this.ServerConfiguration = serverConfiguration;
                poolOfReceiveEventArgs = new SocketAsyncEventArgsPool(serverConfiguration.MaxReceiveConnections);
                receiveEnforcer = new Semaphore(serverConfiguration.MaxReceiveConnections, serverConfiguration.MaxReceiveConnections);
                receiveBufferManager = new BufferManager(serverConfiguration.RecieveBufferSize * serverConfiguration.MaxReceiveConnections, serverConfiguration.RecieveBufferSize);
                sendEnforcer = new Semaphore(serverConfiguration.MaxSendConnections, serverConfiguration.MaxSendConnections);
                if (serverConfiguration.EnablePerformanceCounter)
                    ServerPerformanceCounter = new ServerPerformanceCounter();
                listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
                localEndPoint = new IPEndPoint(IPAddress.Any, ServerConfiguration.ListeningPort);
                listenSocket.Bind(localEndPoint);
            }
            catch (Exception ex)
            {
                Logger.Error(ex.ToString());
            }
        }

        public void Start(Daemon daemon)
        {
            try
            {
                enable = true;
                init = true;         
                receiveBufferManager.Init();
                if (poolOfReceiveEventArgs.Count == 0)
                {
                    for (int i = 0; i < ServerConfiguration.MaxReceiveConnections; i++)
                    {
                        poolOfReceiveEventArgs.Push(CreateReceiveEventArgs());
                    }
                }
                remoteEndPoint = new IPEndPoint(IPAddress.Parse(daemon.Address), daemon.Port);
                StartReceive();
                if (ServerConfiguration.EnablePerformanceCounter)
                    ServerPerformanceCounter.serverStartDateTime = DateTime.Now;
                Logger.Info("Server started, connect to " + daemon);
            }
            catch (Exception ex)
            {
                Logger.Error(ex.ToString());
            }
        }

        public void Stop()
        {
            try
            {
                enable = false;
                
                if (ServerConfiguration.EnablePerformanceCounter)
                    ServerPerformanceCounter.Dispose();

                SocketAsyncEventArgs eventArgs;
                while (poolOfReceiveEventArgs.Count > 0)
                {
                    eventArgs = poolOfReceiveEventArgs.Pop();
                    eventArgs.Dispose();
                }
            }
            catch (Exception ex)
            {
                Logger.Error(ex.ToString());
            }
        }

        private void StartReceive()
        {
            try
            {
                receiveEnforcer.WaitOne();

                SocketAsyncEventArgs receiveEventArg = poolOfReceiveEventArgs.Pop();
                if (receiveEventArg == null)
                {
                    Logger.Error("Can not get receiveEventArg from pool");
                    receiveEventArg = CreateReceiveEventArgs();
                    poolOfReceiveEventArgs.Push(receiveEventArg);
                }

                if (!listenSocket.ReceiveFromAsync(receiveEventArg))
                {
                    ProcessReceive(receiveEventArg);
                }
            }
            catch (Exception ex)
            {
                Logger.Error(ex.ToString());
            }
        }

        public void StartSend(byte[] data)
        {
            if (!init) throw new Exception("Please call Start() to inist server.");

            try
            {
                sendEnforcer.WaitOne();

                SocketAsyncEventArgs sendEventArg = CreateSendEventArgs(data);

                if (!listenSocket.SendToAsync(sendEventArg))
                {
                    ProcessSend(sendEventArg);
                }
            }
            catch (Exception ex)
            {
                Logger.Error(ex.ToString());
            }
        }

        private void LogPackage(string message, byte[] buffer, int offset, int count)
        {
            try
            {
                StringBuilder sb = new StringBuilder();
                sb.AppendFormat("{0} {1} bytes data：", message, count);
                for (int i = 0; i < count; i++)
                {
                    sb.AppendFormat("{0:x2}", buffer[offset + i]);
                    if (i % 2 == 1) sb.Append(" ");
                }
                Logger.Debug(sb.ToString());
            }
            catch (Exception ex)
            {
                Logger.Error(ex.ToString());
            }
        }

        private void ProcessReceive(SocketAsyncEventArgs receiveEventArg)
        {
            if (!enable) return;
            try
            {
                StartReceive();
                if (receiveEventArg.BytesTransferred > 0 && receiveEventArg.SocketError == SocketError.Success)
                {
                    LogPackage("Received", receiveEventArg.Buffer, receiveEventArg.Offset, receiveEventArg.BytesTransferred);
                    var package = AbstractNotifyPackage.Instance.GetPackage(receiveEventArg.Buffer, receiveEventArg.Offset);
                    if (package != null) package.ParseOtherData();
                    else Logger.Warning("Unknown package received!");
                    poolOfReceiveEventArgs.Push(receiveEventArg);
                }
                else
                {
                    Logger.Warning("receiveEventArg.SocketError != SocketError.Success, value is " + receiveEventArg.SocketError.ToString());
                }
                if (ServerConfiguration.EnablePerformanceCounter)
                {
                    Interlocked.Increment(ref ServerPerformanceCounter.totalReceiveCount);
                    Interlocked.Add(ref ServerPerformanceCounter.totalBytesReceived, receiveEventArg.BytesTransferred);
                }
            }
            catch (Exception ex)
            {
                Logger.Error(ex.ToString());
            }
            finally
            {
                receiveEnforcer.Release();
            }
        }

        private void ProcessSend(SocketAsyncEventArgs sendEventArg)
        {
            if (!enable) return;
            try
            {
                if (sendEventArg.SocketError == SocketError.Success)
                {
                    LogPackage("Sent", sendEventArg.Buffer, sendEventArg.Offset, sendEventArg.Count);
                }
                else
                {
                    Logger.Warning("sendEventArg.SocketError != SocketError.Success, value is " + sendEventArg.SocketError.ToString());
                }
                if (ServerConfiguration.EnablePerformanceCounter)
                {
                    Interlocked.Increment(ref ServerPerformanceCounter.totalSentCount);
                    Interlocked.Add(ref ServerPerformanceCounter.totalBytesSent, sendEventArg.Count);
                }
            }
            catch (Exception ex)
            {
                Logger.Error(ex.ToString());
            }
            finally
            {
                sendEnforcer.Release();
            }
        }

        private SocketAsyncEventArgs CreateReceiveEventArgs()
        {
            try
            {
                SocketAsyncEventArgs operationEventArg = new SocketAsyncEventArgs();
                if (!receiveBufferManager.SetBuffer(operationEventArg))
                    Logger.Error("No enough buffer");
                operationEventArg.RemoteEndPoint = localEndPoint;
                operationEventArg.Completed += (sender, e) => ProcessOperation(e);
                return operationEventArg;
            }
            catch (Exception ex)
            {
                Logger.Error(ex.ToString());
            }
            return null;
        }

        private SocketAsyncEventArgs CreateSendEventArgs(byte[] data)
        {
            try
            {
                SocketAsyncEventArgs operationEventArg = new SocketAsyncEventArgs();
                operationEventArg.RemoteEndPoint = remoteEndPoint;
                operationEventArg.SetBuffer(data, 0, data.Length);
                operationEventArg.Completed += (sender, e) => ProcessOperation(e);
                return operationEventArg;
            }
            catch (Exception ex)
            {
                Logger.Error(ex.ToString());
            }
            return null;
        }

        private void ProcessOperation(SocketAsyncEventArgs operationEventArg)
        {
            if (!enable) return;

            try
            {
                switch (operationEventArg.LastOperation)
                {
                    case SocketAsyncOperation.ReceiveFrom:
                        ProcessReceive(operationEventArg);
                        break;
                    case SocketAsyncOperation.SendTo:
                        ProcessSend(operationEventArg);
                        break;
                    default:
                        Logger.Error("The last operation completed on the socket was not a receivefrom or sendto");
                        break;
                }
            }
            catch (Exception ex)
            {
                Logger.Error(ex.ToString());
            }
        }
    }
}
